package com.threeti.mecool.core.application.message;

import com.threeti.mecool.core.domain.model.message.MobileMessage;
import com.threeti.mecool.core.exception.MessageAccountException;
import com.threeti.mecool.core.exception.MessageConnectException;
import com.threeti.mecool.core.exception.MessageException;
import com.threeti.mecool.core.exception.MessageSendException;
import com.threeti.mecool.core.util.AESUtil;
import org.codehaus.jackson.map.ObjectMapper;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.Form;
import org.jivesoftware.smackx.ReportedData;
import org.jivesoftware.smackx.search.UserSearchManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.Iterator;

@Service("messageService")
public class OpenFireMessageService implements MessageService {
    private Logger logger = LoggerFactory.getLogger(OpenFireMessageService.class);
    private String host;
    private int port;
    private String mobileHost;
    private int mobilePort;
    private String service;
    private String username;
    private String password;
    private XMPPConnection conn;
    private ObjectMapper jsonMapper = new ObjectMapper();

    @PostConstruct
    public void init() {
        conn = new XMPPConnection(host);
        try {
            confirmConnection();
        } catch (Exception e) {
            logger.error("connect to OpenFire server", e);
        }
    }

    @Override
    @Transactional
    public void send(MobileMessage msg) throws MessageException {
        confirmAccount(msg.getReceiver());
        String accountName = getAccountName(msg.getReceiver());
        try {
            msg.save();
            String json = jsonMapper.writeValueAsString(msg);
            conn.getChatManager().createChat(accountName, null).sendMessage(json);
        } catch (Exception e) {
            throw new MessageSendException("error sending message", e);
        }
    }

    @Override
    public String getAccountPassword(String userLoginName) {
        userLoginName = userLoginName.toLowerCase();
        return AESUtil.encryptString(userLoginName);
    }

    @Override
    public String getHost() {
        return host;
    }

    @Override
    public int getPort() {
        return port;
    }

    @Override
    public String getMobileHost() {
        return mobileHost;
    }

    @Override
    public int getMobilePort() {
        return mobilePort;
    }

    @Override
    public String getService() {
        return service;
    }

    @Override
    public void saveReceipt(Long messageId, Date receivedDate) {
        MobileMessage mobileMessage = MobileMessage.findById(messageId);
        if (mobileMessage.getReceivedOn() == null) {
            mobileMessage.setReceivedOn(receivedDate.getTime());
            mobileMessage.save();
        }
    }

    @Override
    public void confirmAccount(String userLoginName) throws MessageException {
        final String searchServiceName = "search." + service;
        userLoginName = userLoginName.toLowerCase();
        confirmConnection();
        UserSearchManager userSearchManager = new UserSearchManager(conn);
        try {
            Form searchForm = userSearchManager.getSearchForm(searchServiceName);
            Form answerForm = searchForm.createAnswerForm();
            answerForm.setAnswer("Username", true);
            answerForm.setAnswer("search", userLoginName);
            ReportedData data = userSearchManager.getSearchResults(answerForm, searchServiceName);
            String expectedAccountName = getAccountName(userLoginName);
            Iterator<ReportedData.Row> rows = data.getRows();
            while (rows.hasNext()) {
                ReportedData.Row row = rows.next();
                Iterator accountNames = row.getValues("jid");
                while (accountNames.hasNext()) {
                    String accountName = String.valueOf(accountNames.next());
                    if (accountName.equalsIgnoreCase(expectedAccountName)) {
                        return;
                    }
                }
            }
            conn.getAccountManager().createAccount(userLoginName, getAccountPassword(userLoginName));
        } catch (XMPPException e) {
            throw new MessageAccountException("error checking OpenFire account", e);
        }
    }

    private void confirmConnection() throws MessageException {
        if (!conn.isConnected()) {
            try {
                conn.connect();
                if (!conn.isAuthenticated()) {
                    conn.login(username, password);
                }
            } catch (XMPPException e) {
                logger.error("error connect to OpenFire server - {}:{}:{}:{}",
                        new Object[]{host, port, username, password});
                throw new MessageConnectException(String.format("error connect to OpenFire Server - %s:%s",
                        host, port), e);
            }
        }
    }

    private String getAccountName(String userLoginName) {
        return userLoginName.toLowerCase() + "@" + service;
    }

    //<editor-fold desc="set">
    @Value("${message.server.host}")
    public void setHost(String host) {
        this.host = host.toLowerCase();
    }

    @Value("${message.server.port}")
    public void setPort(int port) {
        this.port = port;
    }

    @Value("${message.server.host.mobile}")
    public void setMobileHost(String mobileHost) {
        this.mobileHost = mobileHost;
    }

    @Value("${message.server.port.mobile}")
    public void setMobilePort(int mobilePort) {
        this.mobilePort = mobilePort;
    }

    @Value("${message.server.service}")
    public void setService(String service) {
        this.service = service;
    }

    @Value("${message.sender.name}")
    public void setUsername(String username) {
        this.username = username;
    }

    @Value("${message.sender.password}")
    public void setPassword(String password) {
        this.password = password;
    }
    //</editor-fold>
}
